/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufrn.siga.component.chart;

import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import javax.swing.SwingWorker;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.DateRange;

/**
 * Classe que permite deslocar o intervalo de exibição do eixo x de um gráfico.
 *
 * @author Teles
 */
public class ShiftableChart extends javax.swing.JPanel {

    /**
     * Intervalo padrão exibido pelo gráfico.
     */
    private long graphicWindown;
    /**
     * Menor data contida no gráfico.
     */
    private long graphicMinimunDate;
    /**
     * Maior data contida no gráfico.
     */
    private long graphicMaximunDate;
    private long lastMinimunVisibleDate;
    private long lastMaximunVisibleDate;
    /**
     * Estabelece o tamanho do deslocamento do grafico;
     */
    private float DISPLACEMENT_PERCENT = 0.1f;
    /**
     * SwingWorker que gerencia o shift do gráfico, quando o usuario pressiona os botões.
     */
    private GraphicShifitingThread graphicShifitingThread;
    /**
     * Eixo que contem o dominio do gráfico.
     */
    private DateAxis domainAxis;

    /**
     * Cria um novo ShiftableChart sem gráfico, apenas com os botões de shift.
     */
    public ShiftableChart() {
        initComponents();
        graphicWindown = -1;
        pnlLayered.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                pnlLayeredComponentResizedEvent();
            }
        });

        pnlLayered.add(pnlChartHolder);
        pnlLayered.setLayer(pnlChartHolder, 1);
        pnlLayered.add(btnScrollLeft);
        pnlLayered.setLayer(btnScrollLeft, 2);
        pnlLayered.add(btnScrollRight);
        pnlLayered.setLayer(btnScrollRight, 2);
    }

    /**
     * Cria um novo ShiftableChart sem a função de deslocar gráficos no tempo.
     *
     * @param chartPanel instancia de {@link ChartPanel} que sera exibida.
     */
    public ShiftableChart(ChartPanel chartPanel) {
        this();
        setChartPanel(chartPanel);
        hideShifitingButtons();
    }

    /**
     * Cria um novo ShiftableChart.
     *
     * @param graphicWindown intervalo de tempo padrão que deve ser exibido pelo gráfico.
     * @param graphicMinimunDate menor data contida no gráfico.
     * @param graphicMaximunDate maior data contaida no gráfico.
     * @param chartPanel ChartPanel que contem o gráfico.
     */
    public ShiftableChart(long graphicWindown, long graphicMinimunDate, long graphicMaximunDate, ChartPanel chartPanel) {
        this();
        this.graphicMinimunDate = graphicMinimunDate;
        this.graphicMaximunDate = graphicMaximunDate;

        setChartPanel(graphicWindown, chartPanel);
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        pnlChartHolder = new javax.swing.JPanel();
        btnScrollLeft = new javax.swing.JButton();
        btnScrollRight = new javax.swing.JButton();
        pnlLayered = new javax.swing.JLayeredPane();

        pnlChartHolder.setBackground(new java.awt.Color(255, 153, 51));
        pnlChartHolder.setOpaque(false);
        pnlChartHolder.setLayout(new java.awt.BorderLayout());

        btnScrollLeft.setBackground(new java.awt.Color(255, 255, 255));
        btnScrollLeft.setIcon(new javax.swing.ImageIcon(getClass().getResource("/dca/sistemascontrole/view/resources/icon_left.png"))); // NOI18N
        btnScrollLeft.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
        btnScrollLeft.setBorderPainted(false);
        btnScrollLeft.setContentAreaFilled(false);
        btnScrollLeft.setFocusPainted(false);
        btnScrollLeft.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnScrollLeftMousePressed(evt);
            }
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                btnScrollLeftMouseReleased(evt);
            }
        });

        btnScrollRight.setBackground(new java.awt.Color(255, 255, 255));
        btnScrollRight.setIcon(new javax.swing.ImageIcon(getClass().getResource("/dca/sistemascontrole/view/resources/icon_right.png"))); // NOI18N
        btnScrollRight.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
        btnScrollRight.setBorderPainted(false);
        btnScrollRight.setContentAreaFilled(false);
        btnScrollRight.setFocusPainted(false);
        btnScrollRight.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnScrollRightMousePressed(evt);
            }
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                btnScrollRightMouseReleased(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(pnlLayered, javax.swing.GroupLayout.DEFAULT_SIZE, 593, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(pnlLayered, javax.swing.GroupLayout.DEFAULT_SIZE, 434, Short.MAX_VALUE)
        );
    }// </editor-fold>//GEN-END:initComponents

    /**
     * Adiciona um novo {@code listener} para capturar os eventos de deslocamento do gráfico.
     *
     * @param listener O {@code listener} que processara o evento.
     */
    public synchronized void addShiftGraphEventListener(ShiftGraphicEventListener listener) {
        if (listener == null) {
            return;
        }
        listenerList.add(ShiftGraphicEventListener.class, listener);
    }

    /**
     * Adiciona um novo {@code listener} para capturar os eventos de zoom do gráfico.
     *
     * @param listener O {@code listener} que processara o evento.
     */
    public synchronized void addZoomEventListener(ZoomEventListener listener) {
        if (listener == null) {
            return;
        }
        listenerList.add(ZoomEventListener.class, listener);
    }

    /**
     * Calcula a porcentagem da janela visível.
     *
     * @param minimunVisibleDate menor valor visível do gráfico.
     * @param maximunVisibleDate maior valor visível do gráfico.
     * @return porcentagem do zoom.
     */
    private double calculateVisiblePercent(long minimunVisibleDate, long maximunVisibleDate) {
        long visibleRange = maximunVisibleDate - minimunVisibleDate;

        return (double) visibleRange / graphicWindown;
    }

    /**
     * Lanca o <i>ShiftGraphicEvent</i>.
     */
    private void fireShiftGraphicEvent(final ShiftGraphicEvent shiftGraphicEvent) {
        ShiftGraphicEventListener[] listeners = listenerList.getListeners(ShiftGraphicEventListener.class);
        for (int i = 0; i < listeners.length; i++) {
            final ShiftGraphicEventListener graphicEventListener = listeners[i];

            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    graphicEventListener.shiftGraphicEvent(shiftGraphicEvent);
                }
            });
        }
    }

    /**
     * Lanca o <i>ZoomEvent</i>.
     */
    private void fireZoomEvent(final ZoomEvent zoomEvent) {
        ZoomEventListener[] listeners = listenerList.getListeners(ZoomEventListener.class);
        for (int i = 0; i < listeners.length; i++) {
            final ZoomEventListener zoomEventListener = listeners[i];

            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    zoomEventListener.zoomEventFired(zoomEvent);
                }
            });
        }
    }

    public long getMinimumVisibleDate() {
        return domainAxis.getMinimumDate().getTime();
    }

    public long getMaximumVisibleDate() {
        return domainAxis.getMaximumDate().getTime();
    }

    /**
     * Esconde os botões de shift.
     */
    public void hideShifitingButtons() {
        pnlLayered.setLayer(btnScrollLeft, 0);
        pnlLayered.setLayer(btnScrollRight, 0);
    }

    /**
     * Exibe os botões de shift.
     */
    public void showShiftingButtons() {
        pnlLayered.setLayer(btnScrollLeft, 2);
        pnlLayered.setLayer(btnScrollRight, 2);
    }

    /**
     * Remove o listener associado ao objeto passado como parametro.
     *
     * @param listener O {@code listener} a ser removido.
     */
    public synchronized void removeShiftGraphEventListener(ShiftGraphicEventListener listener) {
        listenerList.remove(ShiftGraphicEventListener.class, listener);
    }

    /**
     * Remove o listener associado ao objeto passado como parametro.
     *
     * @param listener O {@code listener} a ser removido.
     */
    public synchronized void removeZoomEventListener(ZoomEventListener listener) {
        listenerList.remove(ZoomEventListener.class, listener);
    }

    /**
     * Calcula a porcentagem do zoom.
     *
     * @return porcentagem do zoom.
     */
    public double getZoomPercent() {
        long minimunVisibleDate = domainAxis.getMinimumDate().getTime();
        long maximunVisibleDate = domainAxis.getMaximumDate().getTime();
        long visibleRange = maximunVisibleDate - minimunVisibleDate;

        return (double) visibleRange / graphicWindown;
    }

    /**
     * Interrompe o SwingWorker que desloca o gráfico.
     */
    private void interruptShiftingThread() {
        if (graphicShifitingThread != null && graphicShifitingThread.isRunning()) {
            graphicShifitingThread.interrupt();
        }
    }

    /**
     * Redimensiona o painel que segura o gráfico e reposiciona os botões de shift.
     */
    private void pnlLayeredComponentResizedEvent() {
        Rectangle layeredBounds = pnlLayered.getBounds();
        int width = layeredBounds.width;
        int height = layeredBounds.height;

        pnlChartHolder.setBounds(0, 0, width, height);
        pnlChartHolder.validate();

        width = btnScrollLeft.getPreferredSize().width;
        height = btnScrollLeft.getPreferredSize().height;
        btnScrollLeft.setBounds(pnlLayered.getWidth() - width, 0, width, height);
        btnScrollRight.setBounds(pnlLayered.getWidth() - width, 0 + height, width, height);
    }

    /**
     * Desloca o gráfico.
     *
     * @param shiftDirection sentido do deslocamento.
     */
    public void shiftChart(int shiftDirection) {
        long minimunVisibleDate = domainAxis.getMinimumDate().getTime();
        long maximunVisibleDate = domainAxis.getMaximumDate().getTime();

        double visiblePercent = calculateVisiblePercent(minimunVisibleDate, maximunVisibleDate);
        double displacement = visiblePercent * graphicWindown * DISPLACEMENT_PERCENT;

        if (shiftDirection == ShiftGraphicEvent.SHIFT_RIGHT && maximunVisibleDate >= graphicMaximunDate
                || shiftDirection == ShiftGraphicEvent.SHIFT_LEFT && minimunVisibleDate <= graphicMinimunDate) {
            return;
        }
        displacement *= shiftDirection;

        setVisibleRange((long) (minimunVisibleDate + displacement), (long) (maximunVisibleDate + displacement));
        fireShiftGraphicEvent(new ShiftGraphicEvent(displacement, shiftDirection, DISPLACEMENT_PERCENT));
    }

    /**
     * Atribui um novo ChartPanel.
     *
     * @param graphicWindown janela de exibição do gráfico.
     * @param chartPanel instancia do chartPanel.
     */
    public void setChartPanel(long graphicWindown, ChartPanel chartPanel) {
        setChartPanel(chartPanel);
        graphicMinimunDate = domainAxis.getMinimumDate().getTime();
        graphicMaximunDate = domainAxis.getMaximumDate().getTime();

        if (graphicWindown > 0) {
        } else {
            hideShifitingButtons();
            graphicWindown = graphicMaximunDate - graphicMinimunDate;
        }
        this.graphicWindown = graphicWindown;
        setVisibleRange(graphicMinimunDate, graphicMinimunDate + graphicWindown);
    }

    /**
     * Atribui um novo ChartPanel, sem a funcionlidade de deslocamento do gráfico.
     *
     * @param chartPanel
     */
    public void setChartPanel(ChartPanel chartPanel) {
        XYPlot xyPlot = (XYPlot) chartPanel.getChart().getPlot();
        xyPlot.setFixedDomainAxisSpace(null);
        domainAxis = (DateAxis) xyPlot.getDomainAxis();


        if (pnlChartHolder.getComponentCount() > 0) {
            pnlChartHolder.remove(0);
        }
        pnlChartHolder.add(chartPanel);

        chartPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                long minimunVisibleDate = domainAxis.getMinimumDate().getTime();
                long maximunVisibleDate = domainAxis.getMaximumDate().getTime();

                double visiblePercent = calculateVisiblePercent(minimunVisibleDate, maximunVisibleDate);

                if (visiblePercent > 1 && graphicWindown > 0) {
                    long freeSpace = graphicWindown - (lastMaximunVisibleDate - lastMinimunVisibleDate);
                    minimunVisibleDate = lastMinimunVisibleDate;
                    maximunVisibleDate = lastMaximunVisibleDate;

                    if (freeSpace > 0) {
                        minimunVisibleDate -= freeSpace / 2;
                        maximunVisibleDate += freeSpace / 2;
                    }
                }

                setVisibleRange(minimunVisibleDate, maximunVisibleDate);

                ZoomEvent zoomEvent = new ZoomEvent(visiblePercent, minimunVisibleDate, maximunVisibleDate);
                fireZoomEvent(zoomEvent);
            }
        });
    }

    /**
     * Configura o periodo visivel do gráfico, e configura se os botões de deslocamento devem estar visiveis.
     *
     * @param minimunVisibleDate menor data visivel.
     * @param maximunVisibleDate maior data visivel.
     */
    private void setVisibleRange(long minimunVisibleDate, long maximunVisibleDate) {
        DateRange dateRange = new DateRange(minimunVisibleDate, maximunVisibleDate);
        domainAxis.setRange(dateRange);

        if (minimunVisibleDate <= graphicMinimunDate) {
            pnlLayered.setLayer(btnScrollLeft, 0);
        } else {
            pnlLayered.setLayer(btnScrollLeft, 2);
        }

        if (maximunVisibleDate >= graphicMaximunDate) {
            pnlLayered.setLayer(btnScrollRight, 0);
        } else {
            pnlLayered.setLayer(btnScrollRight, 2);
        }
        lastMinimunVisibleDate = minimunVisibleDate;
        lastMaximunVisibleDate = maximunVisibleDate;
    }

    /**
     * Inicializa o SwingWorker que desloca o gráfico.
     *
     * @param shiftDirection sentido do deslocamento.
     */
    private void starShifitingThread(int shiftDirection) {
        interruptShiftingThread();

        graphicShifitingThread = new GraphicShifitingThread(this, shiftDirection);
        graphicShifitingThread.execute();
    }

    private void btnScrollLeftMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnScrollLeftMouseReleased
        interruptShiftingThread();
    }//GEN-LAST:event_btnScrollLeftMouseReleased

    private void btnScrollRightMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnScrollRightMouseReleased
        interruptShiftingThread();
    }//GEN-LAST:event_btnScrollRightMouseReleased

    private void btnScrollRightMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnScrollRightMousePressed
        shiftChart(ShiftGraphicEvent.SHIFT_RIGHT);
        starShifitingThread(ShiftGraphicEvent.SHIFT_RIGHT);
    }//GEN-LAST:event_btnScrollRightMousePressed

    private void btnScrollLeftMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnScrollLeftMousePressed
        shiftChart(ShiftGraphicEvent.SHIFT_LEFT);
        starShifitingThread(ShiftGraphicEvent.SHIFT_LEFT);
    }//GEN-LAST:event_btnScrollLeftMousePressed
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton btnScrollLeft;
    private javax.swing.JButton btnScrollRight;
    private javax.swing.JPanel pnlChartHolder;
    private javax.swing.JLayeredPane pnlLayered;
    // End of variables declaration//GEN-END:variables
}

/**
 * SwingWorker utilizado para deslocar o gráfico enquanto o usuario estiver pressionado os botoes de shift.
 *
 * @author Teles
 */
class GraphicShifitingThread extends SwingWorker<Object, Object> {

    private boolean isRunning;
    private int shiftDirection;
    private ShiftableChart myChart;
    private int MINIMUN_SLEEP_TIME = 50;

    public GraphicShifitingThread(ShiftableChart myChart, int shiftDirection) {
        this.myChart = myChart;
        this.shiftDirection = shiftDirection;
    }

    @Override
    protected Object doInBackground() throws Exception {
        isRunning = true;
        Thread.sleep(250);
//        Thread.sleep(SLEEP_TIME);

        float i = 1;
        while (isRunning) {
            i += 0.2f;
            publish("");
            int sleepTime = (int) (MINIMUN_SLEEP_TIME + 200 / i);
            Thread.sleep(sleepTime);

        }
//        while (isRunning) {
//            publish("");
//            Thread.sleep(SLEEP_TIME);
//        }

        return "";
    }

    @Override
    protected void process(List<Object> chunks) {
        myChart.shiftChart(shiftDirection);
    }

    public void interrupt() {
        isRunning = false;
    }

    public boolean isRunning() {
        return isRunning;
    }
}
